package util

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/chicken-team-outside/chicken_transmission/client_core/config"
	"github.com/chicken-team-outside/chicken_transmission/driver"
	"github.com/chicken-team-outside/chicken_transmission/driver/adaptor/baidu_netdisk"
	"github.com/chicken-team-outside/chicken_transmission/driver/adaptor/chaoxing_netdisk"
	"github.com/chicken-team-outside/chicken_transmission/model"
	"github.com/chicken-team-outside/chicken_transmission/utils"
	"github.com/gin-gonic/gin"
	"io"
	"net/http"
)

func createBaiduNetDiskDriver(rawAuth json.RawMessage) (driver.NetDiskDriver, error) {
	auth := new(baidu_netdisk.BaiduAuthorizationData)
	err := json.Unmarshal(rawAuth, auth)
	if err != nil {
		return nil, err
	}
	dr := baidu_netdisk.Create(auth.AccessToken)
	return dr, nil
}

func createChaoXingNetDiskDriver(rawAuth json.RawMessage) (driver.NetDiskDriver, error) {
	data := &chaoxing_netdisk.AuthInfo{}
	err := json.Unmarshal(rawAuth, data)
	if err != nil {
		return nil, err
	}
	return chaoxing_netdisk.NewChaoXingNetDiskDriver(data)
}

func CreateNetDiskDriver(info *config.DriverInfo) (driver.NetDiskDriver, error) {
	switch info.Vendor {
	case model.Vendor_BaiduNetDisk:
		return createBaiduNetDiskDriver(info.ConnectionInfo)
	case model.Vendor_ChaoXingNetDisk:
		return createChaoXingNetDiskDriver(info.ConnectionInfo)
	default:
		return nil, fmt.Errorf("unsupported vendor: %v", info.Vendor)
	}
}

type LoginMethod uint8

const (
	LoginMethodQr = LoginMethod(iota)
	LoginMethodCallback
)

type LoginData struct {
	Method        LoginMethod `json:"method"`
	Data          interface{} `json:"data"`
	Desc          string      `json:"desc"`
	CheckInterval int         `json:"check_interval"`
}

func getQrImageBase64(ctx context.Context, imgUrl string) (string, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, imgUrl, nil)
	if err != nil {
		return "", err
	}
	resp, err := utils.GetHttpClient().Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return "", fmt.Errorf("get qr image failed, invalid status code: %v", resp.StatusCode)
	}
	all, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	return "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(all), nil
}

func CreateLoginData(ctx context.Context, vendor model.Vendor) (*LoginData, error) {
	data := &LoginData{Method: LoginMethodQr}
	switch vendor {
	case model.Vendor_BaiduNetDisk:
		rsp, err := baidu_netdisk.CreateQrLoginUrl(ctx)
		if err != nil {
			return nil, err
		}
		var imgBase64 string
		imgBase64, err = getQrImageBase64(ctx, rsp.GetQrcodeUrl())
		if err != nil {
			return nil, fmt.Errorf("get base64 qr image failed: %v", err)
		}
		data.Data = gin.H{
			"img":  imgBase64,
			"data": rsp.GetDeviceCode(),
		}
		data.CheckInterval = 5000
		data.Desc = "请使用百度网盘手机扫码登录, 扫码存在3到5秒延迟, 请等待..."
	case model.Vendor_ChaoXingNetDisk:
		rsp, err := chaoxing_netdisk.GetLoginQr(ctx)
		if err != nil {
			return nil, err
		}
		var imgBase64 string
		imgBase64, err = getQrImageBase64(ctx, rsp.QrUrl)
		if err != nil {
			return nil, fmt.Errorf("get base64 qr image failed: %v", err)
		}
		data.Data = gin.H{
			"img":  imgBase64,
			"data": rsp,
		}
		data.Desc = "请使用超星学习通手机扫码登录, 扫码存在2到3秒延迟, 请等待..."
		rsp.QrUrl = ""
		data.CheckInterval = 3000
	default:
		return nil, nil
	}
	return data, nil
}

func checkChaoXingNetDiskLogin(ctx context.Context, rawAuth json.RawMessage) (json.RawMessage, *LoginData, error) {
	info := &chaoxing_netdisk.QrLoginResponse{}
	err := json.Unmarshal(rawAuth, info)
	if err != nil {
		return nil, nil, err
	}
	status, err := chaoxing_netdisk.GetAuthStatus(ctx, info.Enc, info.Uuid)
	if err != nil {
		if errors.Is(err, chaoxing_netdisk.ErrQrExpired) || errors.Is(err, chaoxing_netdisk.ErrUserCancelLogin) {
			data, err := CreateLoginData(ctx, model.Vendor_ChaoXingNetDisk)
			if err != nil {
				return nil, nil, err
			} else {
				return nil, data, nil
			}
		} else {
			return nil, nil, err
		}
	} else {
		if status == nil {
			return nil, nil, nil
		}
		marshal, err := json.Marshal(status)
		if err != nil {
			return nil, nil, err
		}
		return marshal, nil, nil
	}
}

func checkBaiduNetDiskLogin(ctx context.Context, rawAuth json.RawMessage) (json.RawMessage, *LoginData, error) {
	var deviceCode string
	err := json.Unmarshal(rawAuth, &deviceCode)
	if err != nil {
		return nil, nil, err
	}
	result, err := baidu_netdisk.CheckQrLoginResult(ctx, deviceCode)
	if err != nil {
		if baidu_netdisk.IsQrCodeExpiredOrRevoked(err) {
			data, err := CreateLoginData(ctx, model.Vendor_BaiduNetDisk)
			if err != nil {
				return nil, nil, err
			} else {
				return nil, data, nil
			}
		} else {
			return nil, nil, err
		}
	}
	if result == nil {
		return nil, nil, nil
	}
	marshal, err := json.Marshal(result)
	if err != nil {
		return nil, nil, err
	}
	return marshal, nil, nil
}

func CheckNetDiskLogin(ctx context.Context, vendor model.Vendor, data json.RawMessage) (json.RawMessage, *LoginData, error) {
	switch vendor {
	case model.Vendor_BaiduNetDisk:
		return checkBaiduNetDiskLogin(ctx, data)
	case model.Vendor_ChaoXingNetDisk:
		return checkChaoXingNetDiskLogin(ctx, data)
	default:
		return nil, nil, fmt.Errorf("unsupported vendor: %v", vendor)
	}
}
